/*
 * For legal stuff see the file COPYRIGHT
 */
#import <stdlib.h>
#import <objc/hashtable.h>
#import "ClientInfo.h"
#import "Controller.h"
#import "Session.h"
#import "Expense.h"

@interface ClientInfo(PRIVATE)
- (void)setupSessionList;
- (void)setupExpenseList;
@end

@implementation ClientInfo

- init
{
  [super init];
  [self setupSessionList];
  [self setupExpenseList];
  return self;
}

- free
{
  [[sessionList freeObjects] free];
  sessionList = nil;
  [[expenseList freeObjects] free];
  expenseList = nil;
  return [super free];
}

- (void)computeTotalMins
{
  int i, count = [sessionList count];

  totalMins = 0;
  for ( i = 0; i < count; i++ )
    totalMins += [[sessionList objectAt:i] minutes];
}

/*
 * Called by the controller when a new session has been started.
 */
- addSession:session
{
  [sessionList addObject:session];
  totalMins += [session minutes];
  return self;
}

- deleteSession:session
{
  totalMins -= [session minutes];
  return [sessionList removeObject:session];
}

/*
 * Delete all session and expense data
 */
- (void)deleteSessionsAndExpenses
{
  [sessionList freeObjects];
  [expenseList freeObjects];
  totalMins = 0.0;
}

- (const char *)lastDescription
{
  const char *str = [[sessionList objectAt:0] description];
  return str ? str : "";
}

- (int)sessionCount
{
  return [sessionList count];
}

- sessionAt:(int)position
{
  return [sessionList objectAt:position];
}

/*
 * Called by the controller when a new session has been started.
 */
- addExpense:expense
{
  [expenseList addObject:expense];
  return self;
}

- deleteExpense:expense
{
  return [expenseList removeObject:expense];
}

- (void)deleteAllExpenses
{
  [expenseList freeObjects];
}

- (int)expenseCount
{
  return [expenseList count];
}

- expenseAt:(int)position
{
  return [expenseList objectAt:position];
}

- (void)sortSessions
{
  [sessionList sort];
}

- (void)sortExpenses
{
  [expenseList sort];
}

- (const char *)shortName
{
  return shortName;
}

- (const char *)clientName
{
  return clientName;
}

- (const char *)contactName
{
  return contactName;
}

- (const char *)street
{
  return street;
}

- (const char *)city
{
  return city;
}

- (const char *)addrState	/* "state" is a defined method in Cell...*/
{
  return state;
}

- (const char *)zipCode
{
  return zipCode;
}

- (const char *)faxNumber
{
  return faxNumber;
}

- (const char *)emailAddr
{
  return emailAddr;
}

- (float)hourlyRate
{
  return hourlyRate;
}

- (int)totalMins
{
  return totalMins;
}

/*
 * This is necessary to have the number we use in calculations be
 * (almost) identical to that which is printed on the invoices. It
 * turns out for a total number of hours such as 76.466667, which
 * prints and displays as 76.47 causes enough error when multiplied
 * by an hourly rate that clients noticed the difference. (It happened
 * to be in their favor. This adjusts it to remove the discrepancy.
 * It happens to result in a few more cents in our favor, by the way!
 */
- (float)totalHours
{
  return ((int)((totalMins * 100)/60 + 0.5))/100.0; /* round up then truncate */
}

- (float)totalBillable
{
  return hourlyRate * [self totalHours];
}

/*
 * Compute this on the fly.
 */
- (float)totalExpenses
{
  int i, count = [expenseList count];
  float amount = 0.0;

  for ( i = 0; i < count; i++ ) {
    Expense *expense = [expenseList objectAt:i]; 
    amount += [expense amount];
  }

  return amount;
}

- setShortName:(const char *)str
{
  freeAndCopy( &shortName, str );
  return self;
}

- setClientName:(const char *)str
{
  freeAndCopy( &clientName, str );
  return self;
}

- setContactName:(const char *)str
{
  freeAndCopy( &contactName, str );
  return self;
}

- setStreet:(const char *)str
{
  freeAndCopy( &street, str );
  return self;
}

- setCity:(const char *)str
{
  freeAndCopy( &city, str );
  return self;
}

- setAddrState:(const char *)str
{
  freeAndCopy( &state, str );
  return self;
}

- setZipCode:(const char *)str
{
  freeAndCopy( &zipCode, str );
  return self;
}

- setFaxNumber:(const char *)str
{
  freeAndCopy( &faxNumber, str );
  return self;
}

- setEmailAddr:(const char *)str
{
  freeAndCopy( &emailAddr, str );
  return self;
}

- setHourlyRate:(float)value
{
  hourlyRate = value;
  return self;
}

- setTotalMins:(int)value
{
  totalMins = value;
  return self;
}

/*
 * Compress out consecutive sessions with identical descriptions.
 * This method assumes the list stores sessions in reverse 
 * chronological order.
 */
- (void)compactSessions
{
  int i, earliestPos = [sessionList count] - 1;
  Session *earliest = [sessionList objectAt:earliestPos];

  for ( i = earliestPos - 1; i >= 0; --i ) {
    Session *session;

    session = [sessionList objectAt:i];

    /* Check for consecutive sessions on the same date with the same text */
    if ( strcmp([session description], [earliest description]) == 0 &&
	 strcmp([session startDateString], [earliest startDateString]) == 0 ) {
      [earliest setMinutes:[earliest minutes] + [session minutes]];
      [[sessionList removeObjectAt:i] free];
    } else
      earliest = session;	/* this is our new base case */
  }
}

- (void)exportToFile:(FILE *)fp
{
  int i, count = [sessionList count];

  for ( i = 0; i < count; i++ ) {
    Session *session;

    session = [sessionList objectAt:i];
    fprintf( fp, "%s%c%s%c%s%c%d%c%s\n",
	    shortName, 		       DELIMITER,
	    [session startDateString], DELIMITER,
	    [session startTimeString], DELIMITER,
	    [session minutes], 	       DELIMITER,
	    [session description] );
  }
}

- read: (NXTypedStream *) stream
{
  extern int FileVersion;

  switch ( FileVersion ) {
  case 0:
    NXReadTypes( stream, "********f",
		&clientName, &contactName, &street, &city, &state, &zipCode,
		&faxNumber, &emailAddr, &hourlyRate );

    /* make the short name the same as the full name initially */
    shortName = NXCopyStringBuffer(clientName);
    break;

  default:
    /* Version 1 added shortName for client */
    NXReadTypes( stream, "*********f",
		&shortName, &clientName, &contactName,
		&street, &city, &state, &zipCode,
		&faxNumber, &emailAddr, &hourlyRate );
    break;
  }

  if ( ! sessionList )
    [self setupSessionList];

  [sessionList read:stream];
  [sessionList sort];

  if ( ! expenseList )
    [self setupExpenseList];

  if ( FileVersion >= 2 ) {
    [expenseList read:stream];
    [expenseList sort];
  }

  [self computeTotalMins];
  return self;
}

- write:(NXTypedStream *) stream
{
  NXWriteTypes( stream, "*********f",
	       &shortName, &clientName, &contactName,
	       &street, &city, &state, &zipCode,
	       &faxNumber, &emailAddr, &hourlyRate );
    
  [sessionList write:stream];
  [expenseList write:stream];
  return self;
}

@end


@implementation ClientInfo(PRIVATE)

/*
 * Compare the dates (and if equal, the times) of two
 * sessions and return the proper value to achieve a
 * reverse chronological sort.
 */
- (int)compareSessions:(Session *)obj1 :(Session *)obj2
{
  int date1 = [obj1 dateValue], date2 = [obj2 dateValue];
  
  if ( date1 == date2 )
    return ( [obj1 timeValue] > [obj2 timeValue] ? -1 : 1 );

  return ( date1 > date2 ? -1 : 1 );
}

- (int)compareExpenses:(Expense *)obj1 :(Expense *)obj2
{
  int date1 = [obj1 dateValue], date2 = [obj2 dateValue];
  
  return ( date1 > date2 ? -1 : 1 );
}

- (void)setupSessionList
{
  if ( ! sessionList ) {
    sessionList = [[SortList alloc] init];
    [sessionList setDelegate:self];
    [sessionList setAutoSort:YES];
    [sessionList useComparisonMethod:@selector(compareSessions::)];
  }
}

- (void)setupExpenseList
{
  if ( ! expenseList ) {
    expenseList = [[SortList alloc] init];
    [expenseList setDelegate:self];
    [expenseList setAutoSort:YES];
    [expenseList useComparisonMethod:@selector(compareExpenses::)];
  }
}

@end
